package com.yupi.springbootinit.bizmq;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.github.rholder.retry.*;
import com.google.gson.Gson;
import com.rabbitmq.client.Channel;
import com.yupi.springbootinit.Listener.AIListener;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.exception.ThrowUtils;
import com.yupi.springbootinit.manager.AiManager;
import com.yupi.springbootinit.mapper.ChartMapper;
import com.yupi.springbootinit.model.entity.Chart;
import com.yupi.springbootinit.model.enums.ResultEnum;
import com.yupi.springbootinit.model.vo.BiResponse;
import com.yupi.springbootinit.service.ChartService;
import com.yupi.springbootinit.utils.ExcelUtils;
import com.yupi.springbootinit.utils.InvalidJsonUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
@Slf4j
public class BIMqConsumer {

    @Resource
    private ChartService chartService;

    @Resource
    private ChartMapper chartMapper;

    @Resource
    private AiManager aiManager;


    /**
     * 接收消息的方法
     *
     * @param message     接收到的消息内容，是一个字符串类型
     * @param channel     消息所在的通道，可以通过该通道与 RabbitMQ 进行交互，例如手动确认消息、拒绝消息等
     * @param deliveryTag 消息的投递标签，用于唯一标识一条消息
     */
    // 使用@SneakyThrows注解简化异常处理
    @SneakyThrows
    // 使用@RabbitListener注解指定要监听的队列名称为"code_queue"，并设置消息的确认机制为手动确认
    @RabbitListener(queues = {BIMqConstant.BI_MQ_QUEUE}, ackMode = "MANUAL")
    // @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag是一个方法参数注解,用于从消息头中获取投递标签(deliveryTag),
    // 在RabbitMQ中,每条消息都会被分配一个唯一的投递标签，用于标识该消息在通道中的投递状态和顺序。通过使用@Header(AmqpHeaders.DELIVERY_TAG)注解,可以从消息头中提取出该投递标签,并将其赋值给long deliveryTag参数。
    public void BiReceiveMessage(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        // 使用日志记录器打印接收到的消息内容
        log.info("receiveMessage message = {}", message);
        // 调用业务处理方法
        Long chartId = Long.parseLong(message);
        Chart chart = chartService.getById(chartId);
        if (chart == null) {
            log.error("图表不存在" + chartId);
            channel.basicNack(deliveryTag, false, false);
        }
        List<Map<String, Object>> maps = chartMapper.selectChart(String.valueOf(chart.getId()));
        if (maps == null || maps.isEmpty()) {
            log.error("图表数据为空" + chartId);
            channel.basicNack(deliveryTag, false, false);
        }
        Gson gson = new Gson();
        String resultDate = gson.toJson(maps);
        final String prompt = "你是一个数据分析师和前端开发专家，接下来我会按照以下固定格式给你提供内容：\n" +
                "分析需求：\n" +
                "{数据分析的需求或者目标}\n" +
                "原始数据：\n" +
                "{csv格式的原始数据，用,作为分隔符}\n" +
                "请根据这两部分内容，按照以下指定格式生成回答内容（此外不要输出任何多余的开头、结尾、注释）\n" +
                "【【【【【\n" +
                "{前端 Echarts V5的option配置对象的json代码（只生成option里的代码，不要有option的声明,只能使用双引号”“，代码要简洁规范准确），" +
                "合理地将数据进行可视化，不要生成任何多余的内容，比如注释}\n" +
                "【【【【【\n" +
                "{明确的数据分析结论、越详细越好，不要生成多余的注释}";

        StringBuilder userInput = new StringBuilder();
        userInput.append("分析需求：").append("\n");
        //拼接分析目标
        String userGoal = chart.getGoal();
        String chartType = chart.getChartType();
        if (StringUtils.isNotBlank(chartType)) {
            userGoal = "请使用" + chartType + userGoal;
        }

        userInput.append(userGoal).append("\n");
        userInput.append("原始数据：").append("\n");
        userInput.append(resultDate).append("\n");

        Chart updateChart = new Chart();
        updateChart.setId(chart.getId());
        updateChart.setStatus(ResultEnum.RUNNING.getDes());
        boolean b = chartService.updateById(updateChart);
        if (!b) {

            channel.basicNack(deliveryTag, false, false);
            return;
        }

        // 调用AI接口
        Callable<Boolean> callable = () -> {
            String response = aiManager.doChat(prompt, userInput.toString());
            String[] split = response.split("【【【【【");
            if (split.length < 3) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "AI生成失败，请检查输入数据");
            }
            String genChart = split[1].trim();
            System.out.println(genChart);
            String genResult = split[2].trim();

            Chart updateChartAI = new Chart();
            updateChartAI.setId(chart.getId());
            //校验Json格式
            if (!InvalidJsonUtils.isValidString(genChart)) {
                updateChart.setStatus(ResultEnum.FAILED.getDes());
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "Json格式错误");
            } else {
                updateChart.setGenResult(genResult);
                updateChart.setGenChart(genChart);
                updateChart.setStatus(ResultEnum.SUCCEED.getDes());
            }
            boolean a = chartService.updateById(updateChart);
            if (!a) {
                //handleChartUpdateError(chart.getId(), "更新图表完成状态更新失败");
                log.error("更新图表状态失败" + chartId);
            }
            return true;
        };
        try {
            retryer.call(callable); // 执行
        } catch (RetryException | ExecutionException e) { // 重试次数超过阈值或被强制中断
            channel.basicNack(deliveryTag, false, false);
            e.printStackTrace();
        }


        // 投递标签是一个数字标识,它在消息消费者接收到消息后用于向RabbitMQ确认消息的处理状态。通过将投递标签传递给channel.basicAck(deliveryTag, false)方法,可以告知RabbitMQ该消息已经成功处理,可以进行确认和从队列中删除。
        // 手动确认消息的接收，向RabbitMQ发送确认消息
        channel.basicNack(deliveryTag, false, false);
    }

    @RabbitListener(queues = {BIMqConstant.DEAD_QUEUE_NAME}, ackMode = "MANUAL")
    public void DLXReceiveMessage(String message,Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
        try {
            Long chartId = Long.parseLong(message);
            Chart updateChartResult = new Chart();
            updateChartResult.setId(chartId);
            updateChartResult.setStatus(ResultEnum.FAILED.getDes());
            updateChartResult.setExecMessage("生成图表失败");
            boolean b = chartService.updateById(updateChartResult);
            if (!b) {
                log.error("更新图表状态失败" + chartId);
            }
            channel.basicAck(deliveryTag, false);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
            .retryIfException()
            .retryIfResult(Boolean.FALSE::equals)
            .retryIfRuntimeException() // 发生运行时异常则重试
            .withWaitStrategy(WaitStrategies.incrementingWait(10, TimeUnit.SECONDS, 10, TimeUnit.SECONDS)) // 等待
            .withStopStrategy(StopStrategies.stopAfterAttempt(3)) // 允许执行3次（首次执行 + 最多重试3次）
            .withRetryListener(new AIListener())
            .build();
}
